#include "MD5hashing.hpp"
#include "HashingStrategy.hpp"

#include <cstring>
#include <stdexcept> 

/* Constants for MD5Transform routine. */
#define S11 7
#define S12 12
#define S13 17
#define S14 22
#define S21 5
#define S22 9
#define S23 14
#define S24 20
#define S31 4
#define S32 11
#define S33 16
#define S34 23
#define S41 6
#define S42 10
#define S43 15
#define S44 21

const uint8_t MD5hashing::_PADDING[64] = {
    0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
    0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};

MD5hashing::MD5hashing( void ) : HashingStrategy(16)
{
    this->count[0] = 0;
    this->count[1] = 0;

    // Load magic initialization constants.
    this->state[0] = 0x67452301;
    this->state[1] = 0xefcdab89;
    this->state[2] = 0x98badcfe;
    this->state[3] = 0x10325476;
}

MD5hashing::~MD5hashing( void )
{

}

/* MD5 block update operation. Continues an MD5 message-digest
 * operation, processing another message block, and updating the
 * context.
 **/
void MD5hashing::process( const uint8_t *message_array, size_t length )
{    
    if (this->final_state()) {
        throw std::logic_error("MD5hashing : you cannot process data while you have already finalized hashing");
    }
    
    if (message_array == NULL) {
        throw std::invalid_argument ("MD5hashing : array parameter is a null pointer");
    }
    
    if (length > 0) {
        uint32_t i, index, partLen;

        // Compute number of bytes mod 64
        index = (this->count[0] >> 3) & 0x3F;

        // Update number of bits
        if ( (this->count[0] += (length << 3)) < (length << 3) ) {
            this->count[1]++;
        }

        this->count[1] += (length >> 29);

        partLen = 64 - index;

        // Transform as many times as possible.
        if (length >= partLen) {
            std::memcpy(&this->buffer[index], message_array, partLen);
            this->transform(this->buffer);

            for (i = partLen; i + 63 < length; i += 64) {
                this->transform(&message_array[i]);
            }

            index = 0;
        } else {
            i = 0;
        }

        // Buffer remaining input
        std::memcpy(&this->buffer[index], &message_array[i], length-i);
    }
}

/* MD5 finalization. Ends an MD5 message-digest operation, writing the
 * the message digest and zeroizing the context.
 **/
void MD5hashing::finalize(void) {

    if (!this->final_state()) {
        uint8_t bits[8];
        size_t index, padLen;
        uint8_t digest[16];

        // Save number of bits
        encode(bits, this->count, 8);

        // Pad out to 56 mod 64.
        index = static_cast<size_t>((this->count[0] >> 3) & 0x3f);
        padLen = (index < 56) ? (56 - index) : (120 - index);
        this->process(MD5hashing::_PADDING, padLen);

        // Append length (before padding)
        this->process(bits, 8);

        // Store state in digest
        encode(digest, this->state, 16);
        this->set_digest(digest, 16);

        // Zeroize sensitive information.
        std::memset (this->state, 0, sizeof(this->state));
        std::memset (this->count, 0, sizeof(this->count));
        std::memset (this->buffer, 0, sizeof(this->buffer));

        this->next_state();
    }
}

/* MD5 basic transformation. Transforms state based on block.
 **/
void MD5hashing::transform(const uint8_t block[64])
{
    uint32_t a = this->state[0], b = this->state[1], c = this->state[2], d = this->state[3], x[16];

    decode(x, block, 64);

    // Round 1
    FF (a, b, c, d, x[ 0], S11, 0xd76aa478); // 1
    FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); // 2
    FF (c, d, a, b, x[ 2], S13, 0x242070db); // 3
    FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); // 4
    FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); // 5
    FF (d, a, b, c, x[ 5], S12, 0x4787c62a); // 6
    FF (c, d, a, b, x[ 6], S13, 0xa8304613); // 7
    FF (b, c, d, a, x[ 7], S14, 0xfd469501); // 8
    FF (a, b, c, d, x[ 8], S11, 0x698098d8); // 9
    FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); // 10
    FF (c, d, a, b, x[10], S13, 0xffff5bb1); // 11
    FF (b, c, d, a, x[11], S14, 0x895cd7be); // 12
    FF (a, b, c, d, x[12], S11, 0x6b901122); // 13
    FF (d, a, b, c, x[13], S12, 0xfd987193); // 14
    FF (c, d, a, b, x[14], S13, 0xa679438e); // 15
    FF (b, c, d, a, x[15], S14, 0x49b40821); // 16

    // Round 2
    GG (a, b, c, d, x[ 1], S21, 0xf61e2562); // 17
    GG (d, a, b, c, x[ 6], S22, 0xc040b340); // 18
    GG (c, d, a, b, x[11], S23, 0x265e5a51); // 19
    GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); // 20
    GG (a, b, c, d, x[ 5], S21, 0xd62f105d); // 21
    GG (d, a, b, c, x[10], S22,  0x2441453); // 22
    GG (c, d, a, b, x[15], S23, 0xd8a1e681); // 23
    GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); // 24
    GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); // 25
    GG (d, a, b, c, x[14], S22, 0xc33707d6); // 26
    GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); // 27
    GG (b, c, d, a, x[ 8], S24, 0x455a14ed); // 28
    GG (a, b, c, d, x[13], S21, 0xa9e3e905); // 29
    GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); // 30
    GG (c, d, a, b, x[ 7], S23, 0x676f02d9); // 31
    GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); // 32

    // Round 3
    HH (a, b, c, d, x[ 5], S31, 0xfffa3942); // 33
    HH (d, a, b, c, x[ 8], S32, 0x8771f681); // 34
    HH (c, d, a, b, x[11], S33, 0x6d9d6122); // 35
    HH (b, c, d, a, x[14], S34, 0xfde5380c); // 36
    HH (a, b, c, d, x[ 1], S31, 0xa4beea44); // 37
    HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); // 38
    HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); // 39
    HH (b, c, d, a, x[10], S34, 0xbebfbc70); // 40
    HH (a, b, c, d, x[13], S31, 0x289b7ec6); // 41
    HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); // 42
    HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); // 43
    HH (b, c, d, a, x[ 6], S34,  0x4881d05); // 44
    HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); // 45
    HH (d, a, b, c, x[12], S32, 0xe6db99e5); // 46
    HH (c, d, a, b, x[15], S33, 0x1fa27cf8); // 47
    HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); // 48

    // Round 4
    II (a, b, c, d, x[ 0], S41, 0xf4292244); // 49
    II (d, a, b, c, x[ 7], S42, 0x432aff97); // 50
    II (c, d, a, b, x[14], S43, 0xab9423a7); // 51
    II (b, c, d, a, x[ 5], S44, 0xfc93a039); // 52
    II (a, b, c, d, x[12], S41, 0x655b59c3); // 53
    II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); // 54
    II (c, d, a, b, x[10], S43, 0xffeff47d); // 55
    II (b, c, d, a, x[ 1], S44, 0x85845dd1); // 56
    II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); // 57
    II (d, a, b, c, x[15], S42, 0xfe2ce6e0); // 58
    II (c, d, a, b, x[ 6], S43, 0xa3014314); // 59
    II (b, c, d, a, x[13], S44, 0x4e0811a1); // 60
    II (a, b, c, d, x[ 4], S41, 0xf7537e82); // 61
    II (d, a, b, c, x[11], S42, 0xbd3af235); // 62
    II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); // 63
    II (b, c, d, a, x[ 9], S44, 0xeb86d391); // 64

    this->state[0] += a;
    this->state[1] += b;
    this->state[2] += c;
    this->state[3] += d;

    // Zeroize sensitive information.
    std::memset(x, 0, sizeof(x));
}

/* Encodes input (uint32_t) into output (unsigned char). Assumes len is
 * a multiple of 4.
 **/
void MD5hashing::encode ( uint8_t *output, const uint32_t *input, size_t len )
{
    size_t i, j;

    for (i = 0, j = 0; j < len; i++, j += 4) {
        output[j] = static_cast<uint8_t>(input[i] & 0xff);
        output[j+1] = static_cast<uint8_t>((input[i] >> 8) & 0xff);
        output[j+2] = static_cast<uint8_t>((input[i] >> 16) & 0xff);
        output[j+3] = static_cast<uint8_t>((input[i] >> 24) & 0xff);
    }
}

/* Decodes input (unsigned char) into output (uint32_t). Assumes len is
 * a multiple of 4.
 **/
void MD5hashing::decode ( uint32_t *output, const uint8_t *input, size_t len)
{
    size_t i, j;

    for (i = 0, j = 0; j < len; i++, j += 4) {
        output[i] = static_cast<uint32_t>(input[j]) | (static_cast<uint32_t>(input[j+1]) << 8) | (static_cast<uint32_t>(input[j+2]) << 16) | (static_cast<uint32_t>(input[j+3]) << 24);
    }
}

/******************************************
 * F, G, H and I are basic MD5 functions. *
 ******************************************/
inline uint32_t MD5hashing::F( uint32_t x, uint32_t y, uint32_t z)
{
    return (x & y) | ((~x) & z);
}

inline uint32_t MD5hashing::G( uint32_t x, uint32_t y, uint32_t z)
{
    return (x & z) | (y & (~z));
}

inline uint32_t MD5hashing::H( uint32_t x, uint32_t y, uint32_t z)
{
    return x ^ y ^ z;
}

inline uint32_t MD5hashing::I( uint32_t x, uint32_t y, uint32_t z)
{
    return y ^ (x | (~z));
}

/* FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4.
 * Rotation is separate from addition to prevent recomputation.
 **/
inline void MD5hashing::FF( uint32_t &a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, unsigned int s, uint32_t ac )
{
    a += F(b,c,d) + x + ac;
    a = rotate_left(a,s);
    a += b;
}

inline void MD5hashing::GG( uint32_t &a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, unsigned int s, uint32_t ac )
{
    a += G(b,c,d) + x + ac;
    a = rotate_left(a,s);
    a += b;
}

inline void MD5hashing::HH( uint32_t &a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, unsigned int s, uint32_t ac )
{
    a += H(b,c,d) + x + ac;
    a = rotate_left(a,s);
    a += b;
}

inline void MD5hashing::II( uint32_t &a, uint32_t b, uint32_t c, uint32_t d, uint32_t x, unsigned int s, uint32_t ac )
{
    a += I(b,c,d) + x + ac;
    a = rotate_left(a,s);
    a += b;
}

